// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <utility>

#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "content/browser/frame_host/navigation_request_info.h"
#include "content/browser/loader/navigation_url_loader_impl.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader_delegate_impl.h"
#include "content/browser/streams/stream.h"
#include "content/browser/streams/stream_context.h"
#include "content/browser/streams/stream_registry.h"
#include "content/browser/streams/stream_url_request_job.h"
#include "content/common/navigation_params.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_ui_data.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/browser/stream_handle.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/resource_response.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/test_navigation_url_loader_delegate.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_job.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"

namespace content {

namespace {

    class StreamProtocolHandler
        : public net::URLRequestJobFactory::ProtocolHandler {
    public:
        StreamProtocolHandler(StreamRegistry* registry)
            : registry_(registry)
        {
        }

        // net::URLRequestJobFactory::ProtocolHandler implementation.
        net::URLRequestJob* MaybeCreateJob(
            net::URLRequest* request,
            net::NetworkDelegate* network_delegate) const override
        {
            scoped_refptr<Stream> stream = registry_->GetStream(request->url());
            if (stream.get())
                return new StreamURLRequestJob(request, network_delegate, stream);
            return nullptr;
        }

    private:
        StreamRegistry* registry_;

        DISALLOW_COPY_AND_ASSIGN(StreamProtocolHandler);
    };

    class RequestBlockingResourceDispatcherHostDelegate
        : public ResourceDispatcherHostDelegate {
    public:
        // ResourceDispatcherHostDelegate implementation:
        bool ShouldBeginRequest(const std::string& method,
            const GURL& url,
            ResourceType resource_type,
            ResourceContext* resource_context) override
        {
            return false;
        }
    };

} // namespace

class NavigationURLLoaderTest : public testing::Test {
public:
    NavigationURLLoaderTest()
        : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP)
        , browser_context_(new TestBrowserContext)
    {
        host_.SetLoaderDelegate(&loader_delegate_);
        BrowserContext::EnsureResourceContextInitialized(browser_context_.get());
        base::RunLoop().RunUntilIdle();
        net::URLRequestContext* request_context = browser_context_->GetResourceContext()->GetRequestContext();
        // Attach URLRequestTestJob and make streams work.
        job_factory_.SetProtocolHandler(
            "test", net::URLRequestTestJob::CreateProtocolHandler());
        job_factory_.SetProtocolHandler(
            "blob", base::MakeUnique<StreamProtocolHandler>(StreamContext::GetFor(browser_context_.get())->registry()));
        request_context->set_job_factory(&job_factory_);

        // NavigationURLLoader is only used for browser-side navigations.
        base::CommandLine::ForCurrentProcess()->AppendSwitch(
            switches::kEnableBrowserSideNavigation);
    }

    std::unique_ptr<NavigationURLLoader> MakeTestLoader(
        const GURL& url,
        NavigationURLLoaderDelegate* delegate)
    {
        BeginNavigationParams begin_params(
            std::string(), net::LOAD_NORMAL, false, false,
            REQUEST_CONTEXT_TYPE_LOCATION,
            blink::WebMixedContentContextType::Blockable, url::Origin(url));
        CommonNavigationParams common_params;
        common_params.url = url;
        std::unique_ptr<NavigationRequestInfo> request_info(
            new NavigationRequestInfo(common_params, begin_params, url, true, false,
                false, -1, false, false,
                blink::WebPageVisibilityStateVisible));
        return NavigationURLLoader::Create(browser_context_.get(),
            std::move(request_info), nullptr,
            nullptr, nullptr, delegate);
    }

    // Helper function for fetching the body of a URL to a string.
    std::string FetchURL(const GURL& url)
    {
        net::TestDelegate delegate;
        net::URLRequestContext* request_context = browser_context_->GetResourceContext()->GetRequestContext();
        std::unique_ptr<net::URLRequest> request(
            request_context->CreateRequest(url, net::DEFAULT_PRIORITY, &delegate));
        request->Start();
        base::RunLoop().Run();

        EXPECT_TRUE(request->status().is_success());
        EXPECT_EQ(200, request->response_headers()->response_code());
        return delegate.data_received();
    }

protected:
    TestBrowserThreadBundle thread_bundle_;
    net::URLRequestJobFactoryImpl job_factory_;
    std::unique_ptr<TestBrowserContext> browser_context_;
    LoaderDelegateImpl loader_delegate_;
    ResourceDispatcherHostImpl host_;
};

// Tests that a basic request works.
TEST_F(NavigationURLLoaderTest, Basic)
{
    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(net::URLRequestTestJob::test_url_1(), &delegate);

    // Wait for the response to come back.
    delegate.WaitForResponseStarted();

    // Proceed with the response.
    loader->ProceedWithResponse();

    // Check the response is correct.
    EXPECT_EQ("text/html", delegate.response()->head.mime_type);
    EXPECT_EQ(200, delegate.response()->head.headers->response_code());

    // Check the body is correct.
    EXPECT_EQ(net::URLRequestTestJob::test_data_1(),
        FetchURL(delegate.body()->GetURL()));

    EXPECT_EQ(1, delegate.on_request_handled_counter());
}

// Tests that request failures are propagated correctly.
TEST_F(NavigationURLLoaderTest, RequestFailed)
{
    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(GURL("bogus:bogus"), &delegate);

    // Wait for the request to fail as expected.
    delegate.WaitForRequestFailed();
    EXPECT_EQ(net::ERR_UNKNOWN_URL_SCHEME, delegate.net_error());
    EXPECT_EQ(1, delegate.on_request_handled_counter());
}

// Test that redirects are sent to the delegate.
TEST_F(NavigationURLLoaderTest, RequestRedirected)
{
    // Fake a top-level request. Choose a URL which redirects so the request can
    // be paused before the response comes in.
    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(
        net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate);

    // Wait for the request to redirect.
    delegate.WaitForRequestRedirected();
    EXPECT_EQ(net::URLRequestTestJob::test_url_2(),
        delegate.redirect_info().new_url);
    EXPECT_EQ("GET", delegate.redirect_info().new_method);
    EXPECT_EQ(net::URLRequestTestJob::test_url_2(),
        delegate.redirect_info().new_first_party_for_cookies);
    EXPECT_EQ(302, delegate.redirect_response()->head.headers->response_code());
    EXPECT_EQ(1, delegate.on_request_handled_counter());

    // Wait for the response to complete.
    loader->FollowRedirect();
    delegate.WaitForResponseStarted();

    // Proceed with the response.
    loader->ProceedWithResponse();

    // Check the response is correct.
    EXPECT_EQ("text/html", delegate.response()->head.mime_type);
    EXPECT_EQ(200, delegate.response()->head.headers->response_code());

    // Release the body and check it is correct.
    EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage());
    EXPECT_EQ(net::URLRequestTestJob::test_data_2(),
        FetchURL(delegate.body()->GetURL()));

    EXPECT_EQ(1, delegate.on_request_handled_counter());
}

// Tests that the destroying the loader cancels the request.
TEST_F(NavigationURLLoaderTest, CancelOnDestruct)
{
    // Fake a top-level request. Choose a URL which redirects so the request can
    // be paused before the response comes in.
    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(
        net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate);

    // Wait for the request to redirect.
    delegate.WaitForRequestRedirected();

    // Destroy the loader and verify that URLRequestTestJob no longer has anything
    // paused.
    loader.reset();
    base::RunLoop().RunUntilIdle();
    EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage());
}

// Test that the delegate is not called if OnResponseStarted and destroying the
// loader race.
TEST_F(NavigationURLLoaderTest, CancelResponseRace)
{
    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(
        net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate);

    // Wait for the request to redirect.
    delegate.WaitForRequestRedirected();

    // In the same event loop iteration, follow the redirect (allowing the
    // response to go through) and destroy the loader.
    loader->FollowRedirect();
    loader.reset();

    // Verify the URLRequestTestJob no longer has anything paused and that no
    // response body was received.
    base::RunLoop().RunUntilIdle();
    EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage());
    EXPECT_FALSE(delegate.body());
}

// Tests that the loader may be canceled by context.
TEST_F(NavigationURLLoaderTest, CancelByContext)
{
    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(
        net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate);

    // Wait for the request to redirect.
    delegate.WaitForRequestRedirected();

    // Cancel all requests.
    host_.CancelRequestsForContext(browser_context_->GetResourceContext());

    // Wait for the request to now be aborted.
    delegate.WaitForRequestFailed();
    EXPECT_EQ(net::ERR_ABORTED, delegate.net_error());
    EXPECT_EQ(1, delegate.on_request_handled_counter());
}

// Tests that, if the request is blocked by the ResourceDispatcherHostDelegate,
// the caller is informed appropriately.
TEST_F(NavigationURLLoaderTest, RequestBlocked)
{
    RequestBlockingResourceDispatcherHostDelegate rdh_delegate;
    host_.SetDelegate(&rdh_delegate);

    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(net::URLRequestTestJob::test_url_1(), &delegate);

    // Wait for the request to fail as expected.
    delegate.WaitForRequestFailed();
    EXPECT_EQ(net::ERR_ABORTED, delegate.net_error());
    EXPECT_EQ(1, delegate.on_request_handled_counter());

    host_.SetDelegate(nullptr);
}

// Tests that ownership leaves the loader once the response is received.
TEST_F(NavigationURLLoaderTest, LoaderDetached)
{
    // Fake a top-level request to a URL whose body does not load immediately.
    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(net::URLRequestTestJob::test_url_2(), &delegate);

    // Wait for the response to come back.
    delegate.WaitForResponseStarted();

    // Proceed with the response.
    loader->ProceedWithResponse();

    // Check the response is correct.
    EXPECT_EQ("text/html", delegate.response()->head.mime_type);
    EXPECT_EQ(200, delegate.response()->head.headers->response_code());

    // Destroy the loader.
    loader.reset();
    base::RunLoop().RunUntilIdle();

    // Check the body can still be fetched through the StreamHandle.
    EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage());
    EXPECT_EQ(net::URLRequestTestJob::test_data_2(),
        FetchURL(delegate.body()->GetURL()));
}

// Tests that the request is owned by the body StreamHandle.
TEST_F(NavigationURLLoaderTest, OwnedByHandle)
{
    // Fake a top-level request to a URL whose body does not load immediately.
    TestNavigationURLLoaderDelegate delegate;
    std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(net::URLRequestTestJob::test_url_2(), &delegate);

    // Wait for the response to come back.
    delegate.WaitForResponseStarted();

    // Proceed with the response.
    loader->ProceedWithResponse();

    // Release the body.
    delegate.ReleaseBody();
    base::RunLoop().RunUntilIdle();

    // Verify that URLRequestTestJob no longer has anything paused.
    EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage());
}

} // namespace content
